package cn.yj.common.lock.reentrant;

import cn.yj.common.lock.IVariableLock;
import cn.yj.tools.exception.ServiceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 自定义细粒度锁，目的是根据不同的变量来进行上锁。
 * 将锁的粒度变得更小，提升效率。
 * 比如：根据不同的订单进行上锁；根据不同的用户进行上锁操作
 * 当前缺点：分段锁的数量将是瓶颈，释放锁如果出现问题很有可能会造成内存溢出
 * @author 永健
 * @since 2022-01-21 15:25
 */
public class SyncLocalIVariableLock implements IVariableLock<Serializable> {

    private static final Logger LOGGER = LoggerFactory.getLogger(SyncLocalIVariableLock.class);


    private static final SyncLocalIVariableLock lock = new SyncLocalIVariableLock(true);

    /**
     * 是否是公平锁
     */
    private boolean fair = true;

    private static final int REMOVE_LOCK_TAG = 1;

    /**
     * 分段锁
     * 用于同步获取变量锁
     */
    private SegmentLock<Serializable> segmentLock = new SegmentLock<>();

    /**
     * 锁记录
     */
    private final Map<Serializable, ReentrantLockWrapper> variableLockMap = new ConcurrentHashMap<>();

    public SyncLocalIVariableLock() {
    }

    /**
     *
     * @param fair 是否是公平锁
     */
    public SyncLocalIVariableLock(boolean fair) {
        this.fair = fair;
    }

    @Override
    public void lock(Serializable variable) {
        segmentLock.syncLock(variable);
        ReentrantLockWrapper reentrantLockWrapper;
        try {
            // 根据变量创建锁
            reentrantLockWrapper = this.variableLockMap.get(variable);
            if (reentrantLockWrapper == null) {
                reentrantLockWrapper = new ReentrantLockWrapper(this.fair);
                variableLockMap.put(variable, reentrantLockWrapper);
            } else {
                // 记录当前变量获取锁的次数+1
                reentrantLockWrapper.incrementAndGet();
            }
        } finally {
            // 释放咱们的过度锁
            segmentLock.unLock(variable);
        }
        reentrantLockWrapper.lock();
        LOGGER.info(Thread.currentThread().getName() +" -- get SyncVariableLock,this key is {}",variable);
    }

    @Override
    public void unlock(Serializable variable) {
        ReentrantLockWrapper reentrantLockWrapper = this.variableLockMap.get(variable);
        if (reentrantLockWrapper.getLockCount() == REMOVE_LOCK_TAG) {
            segmentLock.syncLock(variable);
            try {
                if (reentrantLockWrapper.getLockCount() == REMOVE_LOCK_TAG) {
                    this.variableLockMap.remove(variable);
                }
            } finally {
                segmentLock.unLock(variable);
            }
        }
        reentrantLockWrapper.unlock();
        LOGGER.info(Thread.currentThread().getName() +" -- release SyncVariableLock,this key is {}",variable);
    }


    @Override
    public void sync(Serializable val,Runnable runnable){
        try {
            this.lock(val);
            runnable.run();
        } finally {
            this.unlock(val);
        }
    }

    public static void synced(Serializable val,Runnable runnable){
        try {
            lock.lock(val);
            runnable.run();
        } finally {
            lock.unlock(val);
        }
    }

    /**
     * 对锁进行包装多一层。
     * 要记录同一个变量获取锁的次数。
     * 当释放锁的时候，判断该次数为1的时候，就将锁删除。防止堆积造成内存溢出
     */
    private class ReentrantLockWrapper {
        private AtomicInteger lockCount = new AtomicInteger(REMOVE_LOCK_TAG);
        private ReentrantLock lock;

        public ReentrantLockWrapper(boolean fair) {
            lock = new ReentrantLock(fair);
        }

        public void lock() {
            this.lock.lock();
        }

        public void unlock() {
            // 释放锁减1
            this.lockCount.decrementAndGet();
            this.lock.unlock();
        }

        public int incrementAndGet() {
            return this.lockCount.incrementAndGet();
        }

        public int getLockCount() {
            return this.lockCount.get();
        }
    }

    /**
     * 在上业务锁的时候使用
     * 分段锁，用来过度，保证不同的变量进来获取务锁的时候线程安全
     *
     * @param <T>
     */
    private class SegmentLock<T> {
        private int initCount;
        private boolean fair;
        private Map<Integer, ReentrantLock> LOCK_LIST = new ConcurrentHashMap<>();

        public SegmentLock() {
            this(50);
        }

        public SegmentLock(int initCount) {
            this(initCount, true);
        }

        public SegmentLock(Integer initCount, boolean fair) {
            this.initCount = initCount;
            this.fair = fair;

            /**
             * 初始化好锁的数量
             */
            for (int i = 0; i < this.initCount; i++) {
                LOCK_LIST.put(i, new ReentrantLock(this.fair));
            }
        }

        public void syncLock(T key) {
            if (key == null || "".equals(key)) {
                throw new ServiceException("没有获取锁的条件");
            }
            this.LOCK_LIST.get(getIndex(key)).lock();
        }

        /**
         * 根据HashMap的 hashCode 和 下标计算方法实现
         *
         * @param key
         */
        private int getIndex(T key) {
            int h;
            int hashVale = (key == null)? 0 : (h = key.hashCode()) ^ (h >>> 16);
            return (this.initCount - 1) & hashVale;
        }

        public void unLock(T key) {
            if (key == null || "".equals(key)) {
                throw new ServiceException("没有释放锁的条件");
            }
            this.LOCK_LIST.get(getIndex(key)).unlock();
        }
    }
}
